use crate::info::{CurrentProvincePlan, CurrentState, Info, Transport, TravelType, SETTING};
use csv::ReaderBuilder;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub struct ExFun {}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PetKind {
    kind: String,
    chinese_name: String,
    chinese_desc: String,
    spend_perc: String,
    spend_priority: String,
}

// -------------------- province --------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvinceLink {
    pub pid: u32,
    pub name_cn: String,
    pub link_id: u32,
    pub link_name_cn: String,
    pub connect_id: u32,
    pub probability: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransportLink {
    pub pid: u32,
    pub link_id: u32,
    pub connect_id: u32,
    pub transport: Transport,
    pub transport_cn: String,
    pub probability: f32,
    pub time: f32,
    pub money: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Scenery {
    pub sid: u32,
    pub name_cn: String,
    pub probability: f32,
    pub pid: u32,
    pub city_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EconomyLevel {
    pub name_cn: String,
    pub lower: u32,
    pub upper: u32,
    pub level: u32,
    pub scenery_add: u32,
    pub food_add: u32,
}

// -------------------- logic --------------------
pub struct Logic {
    pub info: Info,
    pub play_time: f32,
    pub transport_time: f32,
    pid_2_name: HashMap<u32, String>,
    pid_2_plinks: HashMap<u32, Vec<ProvinceLink>>,
    cid_2_tlinks: HashMap<u32, Vec<TransportLink>>,
    pid_2_sids: HashMap<u32, Vec<u32>>,
    sid_2_scenery: HashMap<u32, Scenery>,
    level_2_economy: HashMap<u32, EconomyLevel>,
    economy_uppers: Vec<u32>,
}

impl Drop for Logic {
    fn drop(&mut self) {
        // println!("-Logic-, [drop]",);
    }
}

impl Logic {
    pub fn new(info: Info) -> Logic {
        Logic {
            info,
            play_time: 0.,
            transport_time: 0.,
            pid_2_name: HashMap::new(),
            pid_2_plinks: HashMap::new(),
            cid_2_tlinks: HashMap::new(),
            pid_2_sids: HashMap::new(),
            sid_2_scenery: HashMap::new(),
            level_2_economy: HashMap::new(),
            economy_uppers: Vec::new(),
        }
    }

    pub fn reset(&mut self, str_pl: String, str_tl: String, str_s: String, str_el: String) {
        self.read_csv(str_pl, str_tl, str_s, str_el);
    }

    // 递归执行step
    pub fn step(&mut self, step_time: f32) {
        self.show_info();

        let mut step_time = step_time;

        if step_time < 0. {
            ExFun::log("-Logic-, Error, wrong step_time:");
            panic!("-Logic-, Error, wrong step_time:{}", step_time);
        }

        // if step_time >= SETTING.max_step_time {
        //     step_time = SETTING.max_step_time;
        // }

        if self.info.base.is_end {
            ExFun::log("-Logic-, is_end");
            return;
        }

        // 递归终止条件
        if self.info.state.time + step_time < self.info.state.need_time {
            self.info.base.game_time += step_time;
            self.info.state.time += step_time;
            self.play(false);
            self.update_show();
            return;
        }

        // 执行递归
        {
            let expend_time = self.info.state.need_time - self.info.state.time;
            self.info.base.game_time += expend_time;
            self.play(true);
            match self.info.state.travel_type {
                TravelType::Play => {
                    self.to_transport();
                }
                TravelType::Transport => {
                    self.out_transport();
                    if self.info.state.pid == SETTING.end_pid {
                        self.to_end();
                    }
                }
                TravelType::End => {
                    panic!("-Logic-, Error, wrong state.travel_type");
                }
            };

            step_time -= expend_time;
            self.update_show();
            self.step(step_time);
        }

        self.update_show();
        return;
    }

    pub fn update_fish_state(&mut self) {
        if !self.info.base.is_fish_ripe {
            if self.info.base.game_time - self.info.base.last_reap_time > SETTING.fish_ripe_time {
                self.info.base.is_fish_ripe = true;
            }
        }
    }

    pub fn reap_fish(&mut self) -> bool {
        if !self.info.base.is_fish_ripe {
            ExFun::log("-Logic-, can not reap!");
            return false;
        }
        self.info.base.is_fish_ripe = false;
        self.info.base.last_reap_time = self.info.base.game_time;
        self.user_add_money(SETTING.fish_reward);
        return true;
    }

    fn to_transport(&mut self) {
        self.play_time += self.info.state.need_time;
        self.info.base.cat_new_money = 0;

        let transport_link = self.choice_transport(self.info.state.pid);

        self.info.state = CurrentState {
            pid: self.info.state.pid, // 在交通工具上时，当前省份为出发时的省份
            travel_type: TravelType::Transport,
            time: 0.,
            need_time: transport_link.time,
            link_index: transport_link.link_id,
            transport: transport_link.transport,
        };

        self.clean_plan();
    }

    fn out_transport(&mut self) {
        self.transport_time += self.info.state.need_time;
        let target_pid = self.info.state.link_index;
        self.info.state = CurrentState {
            pid: target_pid,
            travel_type: TravelType::Play,
            time: 0.,
            need_time: SETTING.play_time,
            link_index: SETTING.none_pid,
            transport: Transport::None,
        };

        self.info.base.cat_new_money = 0;
        self.make_plan();

        self.info.hist.passing_pids.push(target_pid);
        self.info.hist.pid_2_played_sids.push(vec![]);
        let target_pid_name = self.pid_2_name[&target_pid].clone();
        self.info.show.passing_pid_names.push(target_pid_name);
    }

    fn clean_plan(&mut self) {
        self.info.plan = CurrentProvincePlan {
            total_sids: Vec::new(),
            plan_sids: Vec::new(),
            cur_sid_index: 0,
            waster_sid_cnt: 0,
        };
    }

    fn make_plan(&mut self) {
        let pid = self.info.state.pid;
        let total_sids = self.pid_2_sids.get(&pid).expect("pid not exist");
        let num = self.get_to_play_num();

        self.info.plan = CurrentProvincePlan {
            total_sids: total_sids.clone(),
            plan_sids: self.choice_plan_sids(num, &total_sids),
            cur_sid_index: 0,
            waster_sid_cnt: 0,
        };
    }

    fn play(&mut self, pass: bool) {
        match self.info.state.travel_type {
            TravelType::Play => {
                let last_index = self.info.plan.cur_sid_index;
                let cur_index_p = &mut (self.info.plan.cur_sid_index);
                let waster_cnt_p = &mut (self.info.plan.waster_sid_cnt);
                let cat_money_p = &mut (self.info.base.cat_money);
                let max_index = self.info.plan.plan_sids.len();

                if pass {
                    *cur_index_p = max_index;
                } else {
                    // 理论已游玩景点数
                    let new_sid_index =
                        (self.info.state.time / SETTING.time_cost_per_sid).floor() as usize;

                    // 计划景点已经玩完
                    if last_index == max_index {
                        *waster_cnt_p = new_sid_index - max_index;
                    }
                    // 计划景点未玩完
                    else {
                        // 该次时间段不足以把剩下的景点全部玩完
                        if new_sid_index < (max_index + *waster_cnt_p) {
                            let new_sid_cnt = new_sid_index - *waster_cnt_p - last_index;
                            *cur_index_p += new_sid_cnt;
                        }
                        // 该次时间段可以把剩下的景点全部玩完
                        else {
                            *cur_index_p = max_index;
                            *waster_cnt_p = new_sid_index - max_index;
                        }
                    }
                }

                let sid_cnt = (*cur_index_p - last_index) as u32;
                let cost_money = SETTING.money_cost_per_sid * sid_cnt;
                if *cat_money_p < cost_money {
                    *cat_money_p = 0;
                    ExFun::log("-Logic-, cat has no money");
                    println!("-Logic-, cat has no money");
                } else {
                    *cat_money_p -= cost_money;
                }

                for i in last_index..*cur_index_p {
                    let sid = self.info.plan.plan_sids[i];
                    self.update_played_sid(sid);
                }
            }
            TravelType::Transport => {}
            _ => {}
        };
    }

    fn update_played_sid(&mut self, sid: u32) {
        let sid_name = self.sid_2_scenery[&sid].name_cn.clone();
        self.info.hist.total_played_sids.push(sid);
        self.info.show.total_played_sid_names.push(sid_name);

        let len = self.info.hist.pid_2_played_sids.len();
        if len == 0 {
            panic!("");
        }
        self.info.hist.pid_2_played_sids[len - 1].push(sid);
    }

    fn update_show(&mut self) {
        self.info.show.cur_el_name = self.cur_el_name();
        self.info.show.cur_pid_name = self.cur_pid_name();
        self.info.show.cur_sid_name = self.cur_sid_name();
        self.info.show.cur_city_name = self.cur_city_name();
    }

    fn choice_transport(&self, pid: u32) -> TransportLink {
        let plinks = self.pid_2_plinks.get(&pid).expect("links not exist");
        let mut pros = Vec::new();
        for link in plinks {
            pros.push(link.probability);
        }
        let i = Logic::choice_one(&pros);
        let connect_id = plinks.get(i as usize).expect("link not exist").connect_id;

        let tlinks = self.cid_2_tlinks.get(&connect_id).expect("links not exist");
        let mut pros = Vec::new();
        for link in tlinks {
            pros.push(link.probability);
        }
        let i = Logic::choice_one(&pros);
        tlinks.get(i as usize).expect("link not exist").clone()
    }

    fn choice_plan_sids(&self, num: u32, pool_sids: &Vec<u32>) -> Vec<u32> {
        let mut pool: HashMap<u32, f32> = HashMap::new();
        for sid in pool_sids {
            let k = sid;
            let scenery = self.sid_2_scenery.get(k).expect("scenery not exist");
            let v = scenery.probability;
            pool.insert(*k, v);
        }
        let mut plan_sids = Logic::choice_some(num, &pool);
        plan_sids.sort();
        return plan_sids;
    }

    fn get_to_play_num(&self) -> u32 {
        let level = self.get_economy_level();
        let scenery_add = self.level_2_economy[&level].scenery_add;
        let play_cnt = scenery_add + 1;
        // println!("play_cnt: {}", play_cnt);
        return play_cnt;
    }

    pub fn va_sub_vb(va: &Vec<u32>, vb: &Vec<u32>) -> Vec<u32> {
        let mut vc = Vec::new();
        for v in va {
            if !vb.contains(v) {
                vc.push(*v);
            }
        }
        vc
    }

    fn to_end(&mut self) {
        self.info.state = CurrentState {
            pid: SETTING.end_pid,
            travel_type: TravelType::End,
            time: 0.,
            need_time: 1000000.,
            link_index: SETTING.none_pid,
            transport: Transport::None,
        };

        self.update_played_sid(SETTING.end_sid);

        self.info.base.is_end = true;
    }

    pub fn choice_one(pros: &Vec<f32>) -> u32 {
        let len = pros.len();
        let mut ratios: Vec<f32> = Vec::new();

        let mut pro_sum = 0.;
        for pro in pros {
            pro_sum += pro;
            ratios.push(pro_sum);
        }

        let dice = ExFun::random();

        let size = ratios[len - 1];

        let a = (size - 1.).abs();
        if a > 0.0001 {
            panic!("-Logic-, the sum of pro is not 1, but: {}", size);
        }

        let mut res: u32 = 0;
        for r in ratios {
            // 浮点数精度处理
            if dice - r / size < 0.0001 {
                return res;
            }
            res += 1
        }
        res
    }

    fn choice_or_not(p: &f32) -> bool {
        if (p - 1.).abs() < 0.0001 {
            return true;
        }

        let dice = ExFun::random();
        if dice > *p {
            return true;
        }
        return false;
    }

    pub fn choice_some(num: u32, id_2_p: &HashMap<u32, f32>) -> Vec<u32> {
        let mut res = Vec::new();

        for (k, v) in id_2_p {
            if Logic::choice_or_not(v) {
                res.push(*k);
            }
        }

        if res.len() as u32 <= num {
            return res;
        } else {
            ExFun::shuffle(&mut res);
            return res[..(num as usize)].to_vec();
        }
    }

    pub fn cat_receive_money(&mut self, money: u32) {
        self.info.base.cat_money += money;
        self.info.base.cat_new_money += money;

        if self.info.base.cat_new_money >= 500 {
            let new_money = self.info.base.cat_new_money;
            match self.info.state.travel_type {
                TravelType::Play => {
                    let num = new_money / SETTING.money_cost_per_sid;
                    self.info.base.cat_new_money = new_money % SETTING.money_cost_per_sid;
                    let pool =
                        Logic::va_sub_vb(&(self.info.plan.total_sids), &(self.info.plan.plan_sids));
                    let add_sids = self.choice_plan_sids(num, &pool);
                    for sid in add_sids {
                        self.info.plan.plan_sids.push(sid);
                    }
                }
                _ => {}
            }
        }

        self.update_show();
    }

    fn get_economy_level(&self) -> u32 {
        let money = self.info.base.cat_money;
        for i in 0..self.economy_uppers.len() {
            if money < self.economy_uppers[i] {
                return i as u32;
            }
        }
        panic!("-Logic-, wrong money, money: {}", money);
    }

    pub fn cur_el_name(&self) -> String {
        let level = self.get_economy_level();
        self.level_2_economy[&level].name_cn.clone()
    }

    pub fn cur_pid_name(&self) -> String {
        self.pid_2_name.get(&self.info.state.pid).expect("").clone()
    }

    pub fn cur_pid_played_sids(&self) -> Vec<u32> {
        let index = self.info.plan.cur_sid_index;
        if index == 0 {
            return Vec::new();
        }
        // println!("plan_sids: {:?}", self.info.plan.plan_sids);
        // println!("index: {}", index);
        self.info.plan.plan_sids[0..index].to_vec()
    }

    pub fn cur_sid(&self) -> u32 {
        match self.info.state.travel_type {
            TravelType::Play => {
                let cur_index = self.info.plan.cur_sid_index;
                let cnt = self.info.plan.plan_sids.len();
                if cnt == 0 {
                    return SETTING.none_sid;
                }
                if cur_index == cnt {
                    return self.info.plan.plan_sids[cnt - 1];
                }

                return self.info.plan.plan_sids[cur_index];
            }
            TravelType::Transport => {
                return SETTING.none_sid;
            }
            _ => SETTING.end_sid,
        }
    }

    fn cur_scenery(&self) -> Scenery {
        let sid = self.cur_sid();
        if sid == SETTING.none_sid {
            return Scenery {
                sid: SETTING.none_sid,
                name_cn: SETTING.none_sid_name.to_string(),
                probability: 0.,
                pid: SETTING.none_pid,
                city_name: SETTING.none_city_name.to_string(),
            };
        };

        self.sid_2_scenery.get(&sid).expect("").clone()
    }

    pub fn cur_sid_name(&self) -> String {
        self.cur_scenery().name_cn
    }

    pub fn cur_city_name(&self) -> String {
        self.cur_scenery().city_name
    }

    pub fn get_played_sids_by_pid(&self, pid: u32) -> Vec<u32> {
        let len = self.info.hist.passing_pids.len();
        for i in 0..len {
            if self.info.hist.passing_pids[i] == pid {
                return self.info.hist.pid_2_played_sids[i].clone();
            }
        }
        ExFun::log("-Logic-, error, wrong pid ");
        return vec![];
    }

    pub fn user_add_money(&mut self, money: u32) {
        self.info.base.user_money += money;
    }

    pub fn user_sub_money(&mut self, money: u32) {
        if money > self.info.base.user_money {
            ExFun::log("-Logic-, error, wrong money ");
            panic!()
        }
        self.info.base.user_money -= money;
    }

    fn show_info(&self) {
        // println!("====================");
        // println!("游戏时长: {}", self.info.base.game_time);
        // println!("当前省份: {}", self.info.show.cur_pid_name);
        // println!("已玩省份: {:?}", self.info.show.passing_pid_names);
        // println!("旅行状态: {:?}", self.info.state.travel_type);
        // println!("状态时间: {}", self.info.state.time);
        // println!("当前金钱: {}", self.info.base.cat_money);
        // println!("当前景点: {}", self.info.show.cur_sid_name);
        // println!("景点计划: {:?}", self.info.plan.plan_sids);
        // println!("当前已玩: {:?}", self.cur_pid_played_sids());
        // println!("全部已玩: {:?}", self.info.hist.total_played_sids);
        // println!("====================");
        // println!("base: {:?}", self.info.base);
        // println!("state: {:?}", self.info.state);
        // println!("hist: {:?}", self.info.hist);
        // println!("show: {:?}", self.info.show);
    }

    fn read_csv(&mut self, str_pl: String, str_tl: String, str_s: String, str_el: String) {
        {
            ExFun::log("-Logic-, [read_csv]: -01- EasternProvinceLink");

            let mut rdr = ReaderBuilder::new()
                .delimiter(b',')
                .from_reader(str_pl.as_bytes());

            for result in rdr.deserialize::<ProvinceLink>() {
                let line = result.expect("-Logic-, [read_csv]: deserialize failed");
                let pid = line.pid;
                self.pid_2_name.entry(pid).or_insert(line.name_cn.clone());
                self.pid_2_name
                    .entry(line.link_id)
                    .or_insert(line.link_name_cn.clone());
                let provice_links = self.pid_2_plinks.entry(pid).or_insert(vec![]);
                provice_links.push(line);

                // println!("{:?}", line);
            }
        }

        {
            ExFun::log("-Logic-, [read_csv]: -02- EasternTransportLink");

            let mut rdr = ReaderBuilder::new()
                .delimiter(b',')
                .from_reader(str_tl.as_bytes());

            for result in rdr.deserialize::<TransportLink>() {
                let line = result.expect("-Logic-, [read_csv]: deserialize failed");
                let connect_id = line.connect_id;
                let transport_links = self.cid_2_tlinks.entry(connect_id).or_insert(vec![]);
                transport_links.push(line);

                // println!("{:?}", line);
            }
        }

        {
            ExFun::log("-Logic-, [read_csv]: -03- EasternScenery");

            let mut rdr = ReaderBuilder::new()
                .delimiter(b',')
                .from_reader(str_s.as_bytes());

            for result in rdr.deserialize::<Scenery>() {
                let line = result.expect("-Logic-, [read_csv]: deserialize failed");
                let pid = line.pid;
                let sid = line.sid;
                let sid_s = self.pid_2_sids.entry(pid).or_insert(vec![]);
                sid_s.push(sid);

                self.sid_2_scenery.insert(sid, line);

                // println!("{:?}", line);
            }
        }

        {
            ExFun::log("-Logic-, [read_csv]: -04- EconomyLevel");

            let mut rdr = ReaderBuilder::new()
                .delimiter(b',')
                .from_reader(str_el.as_bytes());

            for result in rdr.deserialize::<EconomyLevel>() {
                let line = result.expect("-Logic-, [read_csv]: deserialize failed");
                let level = line.level;
                let upper = line.upper;
                self.level_2_economy.entry(level).or_insert(line);
                self.economy_uppers.push(upper);

                // println!("{:?}", line);
            }
        }
    }
}
